/*
Miranda IM for Windows Search

Copyright (C) 2012-2014 Nikolay Raspopov

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "stdafx.h"
#include "MirandaPlugin.h"
#include "MirandaSearch.h"
#include "Strings.h"

#define DefineHook(event)			static HANDLE h##event = NULL; static int On##event(WPARAM wParam, LPARAM lParam);
#define SetHook(event)				h##event = HookEvent( (event), On##event ); ATLASSERT( h##event );
#define RemoveHook(event)			if ( h##event ) { UnhookEvent( h##event ); h##event = NULL; }

#define DefineService(service)		static HANDLE h##service = NULL; static INT_PTR On##service(WPARAM wParam, LPARAM lParam);
#define SetService(service)			h##service = CreateServiceFunction( _AtlModule.GetProductName() + "/" #service, On##service ); ATLASSERT( h##service );
#define RemoveService(service)		if ( h##service ) { DestroyServiceFunction( h##service ); h##service = NULL; }

#define IsCommand(request,command)	((request).GetCount() == _countof(command) - 1 && memcmp( (request).GetData(), (command), _countof(command) - 1 ) == 0)

#define GetContactCount()			((DWORD)  CallService( MS_DB_CONTACT_GETCOUNT, 0, 0 ))
#define GetFirstContact()			((HANDLE) CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 ))
#define GetNextContact(contact)		((HANDLE) CallService( MS_DB_CONTACT_FINDNEXT, (WPARAM)(contact), 0 ))
#define GetContactName(contact)		((LPCWSTR)CallService( MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)(contact), GCDNF_UNICODE))
#define GetEventCount(contact)		((DWORD)  CallService( MS_DB_EVENT_GETCOUNT, (WPARAM)(contact), 0 ))
#define GetFirstEvent(contact)		((HANDLE) CallService( MS_DB_EVENT_FINDFIRST, (WPARAM)(contact), 0 ))
#define GetLastEvent(contact)		((HANDLE) CallService( MS_DB_EVENT_FINDLAST, (WPARAM)(contact), 0 ))
#define GetNextEvent(event)			((HANDLE) CallService( MS_DB_EVENT_FINDNEXT, (WPARAM)(event), 0 ))
#define GetPrevEvent(event)			((HANDLE) CallService( MS_DB_EVENT_FINDPREV, (WPARAM)(event), 0 ))
#define GetEventSize(event)			((int)    CallService( MS_DB_EVENT_GETBLOBSIZE, (WPARAM)(event), 0 ))
#define GetEvent(event,dbei)		((int)    CallService( MS_DB_EVENT_GET, (WPARAM)(event), (LPARAM)(dbei) ))

MM_INTERFACE		mmi = {};
PLUGINLINK*			pluginLink = NULL;
PLUGININFOEX		pluginInfo = { sizeof( PLUGININFOEX ), NULL, 0, NULL, NULL, NULL, NULL, NULL, UNICODE_AWARE, 0, MIID_SEARCH };
int					hLangpack = 0;
CAtlList< CString >	g_oContacts;		// Contacts name cache to detect nick changes

DefineHook( ME_SYSTEM_MODULESLOADED )
DefineHook( ME_DB_EVENT_ADDED )
DefineHook( ME_DB_EVENT_DELETED )
DefineHook( ME_DB_CONTACT_ADDED )
DefineHook( ME_DB_CONTACT_DELETED )
DefineHook( ME_DB_CONTACT_SETTINGCHANGED )

// Re-index messages of specified contact only
// wParam=hContact (NULL - all contacts)
// lParam=0
// Returns 0
DefineService( ReIndex )


void ReloadContacts()
{
	g_oContacts.RemoveAll();

	for ( HANDLE hContact = GetFirstContact(); hContact; hContact = GetNextContact( hContact ) )
	{
		if ( LPCWSTR wszName = GetContactName( hContact ) )
		{
			g_oContacts.AddTail( wszName );
		}
	}
}

CString GetMissingContactName()
{
	for ( HANDLE hContact = GetFirstContact(); hContact; hContact = GetNextContact( hContact ) )
	{
		if ( LPCWSTR wszName = GetContactName( hContact ) )
		{
			if ( POSITION pos = g_oContacts.Find( wszName ) )
			{
				g_oContacts.RemoveAt( pos );
			}
		}
	}

	return g_oContacts.GetCount() ? g_oContacts.RemoveHead() : CString();
}

DWORD GetEventTime( HANDLE hEvent )
{
	DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
	if ( ! GetEvent( hEvent, &dbei ) )
		return dbei.timestamp;
	return 0;
}

int GetRealEventCount( HANDLE hContact )
{
	int count = 0;
	for ( HANDLE hCurEvent = GetFirstEvent( hContact ); hCurEvent; hCurEvent = GetNextEvent( hCurEvent ) )
	{
		const int nSize = GetEventSize( hCurEvent );
		if ( nSize > 0 )
		{
			DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
			if ( ! GetEvent( hCurEvent, &dbei ) && dbei.eventType == EVENTTYPE_MESSAGE )
			{
				++count;
			}
		}
	}
	return count;
}

int GetEventNumber( HANDLE hContact, HANDLE hEvent )
{
	int index = 1;
	for ( HANDLE hCurEvent = GetFirstEvent( hContact ); hCurEvent; hCurEvent = GetNextEvent( hCurEvent ) )
	{
		const int nSize = GetEventSize( hCurEvent );
		if ( nSize > 0 )
		{
			DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
			if ( ! GetEvent( hCurEvent, &dbei ) && dbei.eventType == EVENTTYPE_MESSAGE )
			{
				if ( hCurEvent == hEvent )
					return index;

				++index;
			}
		}
	}
	return -1;
}

BOOL Send(HANDLE hPipe, const void* pCommand, DWORD dwSize)
{
	ATLTRACE( "Send() : %u bytes\n", dwSize );

	DWORD cbWritten;
	if ( ! WriteFile( hPipe, &dwSize, sizeof( DWORD ), &cbWritten, NULL ) || cbWritten != sizeof( DWORD ) )
		return FALSE;

	if ( dwSize )
	{
		if ( ! WriteFile( hPipe, pCommand, dwSize, &cbWritten, NULL ) || cbWritten != dwSize )
			return FALSE;
	}

	return TRUE;
}

BOOL Recv(HANDLE hPipe, CAtlArray< BYTE >& pBuf)
{
	pBuf.RemoveAll();

	DWORD dwSize;

	DWORD cbRead;
	if ( ! ReadFile( hPipe, &dwSize, sizeof( DWORD ), &cbRead, NULL ) || cbRead != sizeof( DWORD ) || dwSize > 0x40000000 ) // 1 GB limit
		return FALSE;

	if ( dwSize )
	{
		pBuf.SetCount( dwSize );
		if ( ! ReadFile( hPipe, pBuf.GetData(), dwSize, &cbRead, NULL ) || cbRead != dwSize )
			return FALSE;
	}

	return TRUE;
}

void StartIndex(void*)
{
	CallService( MS_SYSTEM_THREAD_PUSH, 0, 0 );

	CoInitialize( NULL );

	Sleep( 1000 );

	// Test for registration
	CComPtr< IProgressDialog > pProgress;
	for (;;)
	{
		HKEY hKey;

		// MirandaProtocol
		if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, _T("CLSID\\{1E5FCA4F-79CB-423B-A6CE-678DD6F574C8}"), 0,
			KEY_NOTIFY | ( Is64Windows() ? KEY_WOW64_64KEY : 0 ), &hKey ) == ERROR_SUCCESS )
		{
			RegCloseKey( hKey );
			// MirandaAccessor
			if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, _T("CLSID\\{9883EC9C-FBF2-431E-B3A4-15700A9422FD}"), 0,
				KEY_NOTIFY | ( Is64Windows() ? KEY_WOW64_64KEY : 0 ), &hKey ) == ERROR_SUCCESS )
			{
				RegCloseKey( hKey );
				// MirandaFilter
				if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, _T("CLSID\\{65E4D50B-CF07-4A10-9F81-B91FD5B68674}"), 0,
					KEY_NOTIFY | ( Is64Windows() ? KEY_WOW64_64KEY : 0 ), &hKey ) == ERROR_SUCCESS )
				{
					RegCloseKey( hKey );
					// Protocol
					if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows Search\\Gather\\Windows\\SystemIndex\\Protocols\\miranda"), 0,
						KEY_NOTIFY | ( Is64Windows() ? KEY_WOW64_64KEY : 0 ), &hKey ) == ERROR_SUCCESS )
					{
						RegCloseKey( hKey );
						// Associations
						if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, _T( "Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\miranda" ), 0,
							KEY_NOTIFY | ( Is64Windows() ? KEY_WOW64_64KEY : 0 ), &hKey ) == ERROR_SUCCESS )
						{
							RegCloseKey( hKey );							

							// All OK
							break;
						}
					}
				}
			}
		}

		if ( MessageBox( GetDesktopWindow(), TranslateT("Need to register Miranda Search plugin. Please use account with administrative privileges."),
			CString( _AtlModule.GetProductName() ), MB_OKCANCEL | MB_ICONEXCLAMATION ) != IDOK )
			// Skip by user
			break;

		if ( ! pProgress ) pProgress.CoCreateInstance( CLSID_ProgressDialog );
		if ( pProgress )
		{
			pProgress->SetTitle( CString( _AtlModule.GetProductName() ) );
			pProgress->StartProgressDialog( GetDesktopWindow(), NULL, PROGDLG_NORMAL | PROGDLG_NOCANCEL | PROGDLG_NOTIME | PROGDLG_MARQUEEPROGRESS, NULL );
		}

		CString sFile32, sFile64;
#ifdef _WIN64
		sFile64 = _AtlModule.m_sModule;
		sFile32 = sFile64.Left( sFile64.GetLength() - 6 ) + _T( "32.dll" );
#else
		sFile32 = _AtlModule.m_sModule;
		sFile64 = sFile32.Left( sFile32.GetLength() - 6 ) + _T( "64.dll" );
#endif

		SHELLEXECUTEINFO sei = { sizeof( SHELLEXECUTEINFO ), SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS, GetDesktopWindow() };
		if ( _AtlModule.m_OSVersion.dwMajorVersion >= 6 )
			sei.lpVerb = _T("runas");

		if ( pProgress ) pProgress->SetLine( 1, TranslateT( "Registering 32-bit..." ), FALSE, NULL );
		if ( GetFileAttributes( sFile32 ) != INVALID_FILE_ATTRIBUTES )
		{
			CString sParams = _T( "/s \"" ) + sFile32 + _T( "\"" );
			sei.lpFile = _T( "regsvr32.exe" );
			sei.lpParameters = sParams;
			if ( ShellExecuteEx( &sei ) && sei.hProcess )
			{
				WaitForSingleObject( sei.hProcess, INFINITE );
				CloseHandle( sei.hProcess );
			}
			sei.hProcess = NULL;
			sei.hInstApp = NULL;
		}

		if ( Is64Windows() )
		{
			if ( pProgress ) pProgress->SetLine( 1, TranslateT( "Registering 64-bit..." ), FALSE, NULL );
			if ( GetFileAttributes( sFile64 ) != INVALID_FILE_ATTRIBUTES )
			{
				CString sParams = _T("/s \"") + sFile64 + _T("\"");
				sei.lpFile = _T( "regsvr32.exe" );
				sei.lpParameters = sParams;
				if ( ShellExecuteEx( &sei ) && sei.hProcess )
				{
					WaitForSingleObject( sei.hProcess, INFINITE );
					CloseHandle( sei.hProcess );
				}
				sei.hProcess = NULL;
				sei.hInstApp = NULL;
			}
		}

		if ( pProgress ) pProgress->SetLine( 1, TranslateT( "Registering Windows Search..." ), FALSE, NULL );
		CString sMSI = _AtlModule.m_sModule.Left( _AtlModule.m_sModule.ReverseFind( _T( '\\' ) ) + 1 ) + DATA_DIR _T("\\") MSI_FILE;
		if ( GetFileAttributes( sMSI ) != INVALID_FILE_ATTRIBUTES )
		{
			CString sParams = _T( "/i \"" ) + sMSI + _T( "\" /q" );
			sei.lpFile = _T( "msiexec.exe" );
			sei.lpParameters = sParams;
			if ( ShellExecuteEx( &sei ) && sei.hProcess )
			{
				WaitForSingleObject( sei.hProcess, INFINITE );
				CloseHandle( sei.hProcess );
			}
			sei.hProcess = NULL;
			sei.hInstApp = NULL;
		}

		if ( pProgress ) pProgress->StopProgressDialog();
	}

	// Check for search root and create it if not exist
	if ( ManageURL( TRUE, FALSE, GetCurrentUserURL() ) == S_FALSE )
	{
		// Re-crawl existing search root
		const DWORD nContactCount = GetContactCount();
		const DWORD nSavedContactCount = DBGetContactSettingDword( NULL, _AtlModule.GetProductName(), "ContactCount", 0 );
		if ( nSavedContactCount != nContactCount )
		{
			DBWriteContactSettingDword( NULL, _AtlModule.GetProductName(), "ContactCount", nContactCount );

			OnURLChanged( GetCurrentUserURL(), SEARCH_CHANGE_MODIFY );
		}

		for ( HANDLE hContact = GetFirstContact(); hContact; hContact = GetNextContact( hContact ) )
		{
			const DWORD nEventCount = GetEventCount( hContact );
			const DWORD nEventTime = GetEventTime( GetLastEvent( hContact ) );
			const DWORD nSavedEventCount = DBGetContactSettingDword( hContact, _AtlModule.GetProductName(), "EventCount", 0 );
			const DWORD nSavedEventTime = DBGetContactSettingDword( hContact, _AtlModule.GetProductName(), "EventTime", 0 );
			if ( nSavedEventCount != nEventCount || nSavedEventTime != nEventTime )
			{
				DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventCount", nEventCount );
				DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventTime", nEventTime );

				if ( GetRealEventCount( hContact ) > 0 )
				{
					if ( LPCWSTR wszName = GetContactName( hContact ) )
					{
						const CString sURL = GetCurrentUserURL() + URLEncode( wszName ) + _T( "/" );
						OnURLChanged( sURL, SEARCH_CHANGE_MODIFY );
					}
				}
			}
		}
	}

	CoUninitialize();

	CallService( MS_SYSTEM_THREAD_POP, 0, 0 );
}

BOOL OnWhoAmIRequest( HANDLE hPipe )
{
	ATLTRACE( "PipeCommand() : Who am I request\n" );

	return Send( hPipe, GetContactName( NULL ) );
}

BOOL OnVersionRequest( HANDLE hPipe )
{
	ATLTRACE( "PipeCommand() : Version request\n" );

	return Send( hPipe, _AtlModule.m_nVersion, sizeof( _AtlModule.m_nVersion ) );
}

BOOL OnListRequest( HANDLE hPipe )
{
	CAtlArray< BYTE > pContacts;

	int nCount = 0;
	for ( HANDLE hContact = GetFirstContact(); hContact; hContact = GetNextContact( hContact ) )
	{
		if ( GetRealEventCount( hContact ) > 0 )
		{
			if ( LPCWSTR wszName = GetContactName( hContact ) )
			{
				size_t nNameSize = ( wcslen( wszName ) + 1 ) * sizeof( WCHAR );

				size_t nOldSize = pContacts.GetCount();
				pContacts.SetCount( nOldSize + sizeof( ListData ) + nNameSize );

				ListData& data = *(ListData*)( pContacts.GetData() + nOldSize );
				data.tTimestamp = GetEventTime( GetLastEvent( hContact ) );
				memcpy_s( data.wszName, nNameSize, wszName, nNameSize );

				++nCount;
			}
		}
	}

	ATLTRACE( "PipeCommand() : Contact list request (found %d contacts)\n", nCount );

	return Send( hPipe, pContacts );
}

BOOL OnEventRequest( HANDLE hPipe, const BYTE* pRequest, size_t nLength )
{
	CString sName;
	int nEventID = 0;
	if ( pRequest[ nLength - 1 ] == '/' )
	{
		// URL: "/{contact}/"
		sName = URLDecode( CStringA( (LPCSTR)pRequest + 1, (int)nLength - 2 ) );
	}
	else
	{
		// URL: "/{contact}/{event id}"
		const BYTE* pEvent = (const BYTE*)memchr( pRequest + 1, '/', nLength - 1 );
		if ( ! pEvent )
			return true;

		const size_t nNameLen = pEvent - pRequest - 1;
		sName = URLDecode( CStringA( (LPCSTR)pRequest + 1, (int)nNameLen ) );
		CStringA sEventID( (LPCSTR)pEvent + 1, (int)( nLength - nNameLen ) - 2 );
		if ( sEventID.CompareNoCase( "last" ) == 0 )
		{
			// Last index
			nEventID = -1;
		}
		else
		{
			nEventID = atoi( sEventID );
			if ( nEventID <= 0 )
			{
				// Wrong index
				sName.Empty();
			}
		}
	}

	HANDLE hFoundContact = NULL;
	if ( ! sName.IsEmpty() )
	{
		for ( HANDLE hContact = GetFirstContact(); hContact; hContact = GetNextContact( hContact ) )
		{
			if ( LPCWSTR wszName = GetContactName( hContact ) )
			{
				if ( sName.CompareNoCase( wszName ) == 0 )
				{
					hFoundContact = hContact;
					break;
				}
			}
		}
	}

	CAtlArray< BYTE > pEvent;
	if ( hFoundContact )
	{
		if ( nEventID == -1 )
		{
			// Get last event of contact
			for ( HANDLE hEvent = GetLastEvent( hFoundContact ); hEvent; hEvent = GetPrevEvent( hEvent ) )
			{
				const int nSize = GetEventSize( hEvent );
				if ( nSize > 0 )
				{
					DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
					if ( ! GetEvent( hEvent, &dbei ) && dbei.eventType == EVENTTYPE_MESSAGE )
					{
						CAutoVectorPtr< BYTE > pBuf( new BYTE[ nSize ] );
						DBEVENTINFO dbeiData = { sizeof( DBEVENTINFO ), NULL, 0, 0, 0, (DWORD)nSize, pBuf };
						if ( ! GetEvent( hEvent, &dbeiData ) )
						{
							if ( LPWSTR szMsg = DbGetEventTextW( &dbeiData, CP_ACP ) )
							{
								size_t nMsgSize = ( wcslen( szMsg ) + 1 ) * sizeof( WCHAR );
								pEvent.SetCount( sizeof( EventData ) + nMsgSize );

								EventData& data = *(EventData*)pEvent.GetData();
								data.nFlags = dbeiData.flags;
								data.tTimestamp = dbeiData.timestamp;
								memcpy_s( data.wszText, nMsgSize, szMsg, nMsgSize );

								mir_free( szMsg );
							}
						}
						break;
					}
				}
			}

			ATLTRACE( "PipeCommand() : Last event request from \"%s\"\n", (LPCSTR)CT2A( (LPCTSTR)sName ) );
		}
		else if ( nEventID > 0 )
		{
			// Get event
			int nCurrentEventID = 0;
			for ( HANDLE hEvent = GetFirstEvent( hFoundContact ); hEvent; hEvent = GetNextEvent( hEvent ) )
			{
				const int nSize = GetEventSize( hEvent );
				if ( nSize > 0 )
				{
					DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
					if ( ! GetEvent( hEvent, &dbei ) && dbei.eventType == EVENTTYPE_MESSAGE )
					{
						if ( ++nCurrentEventID == nEventID )
						{
							CAutoVectorPtr< BYTE > pBuf( new BYTE[ nSize ] );
							DBEVENTINFO dbeiData = { sizeof( DBEVENTINFO ), NULL, 0, 0, 0, (DWORD)nSize, pBuf };
							if ( ! GetEvent( hEvent, &dbeiData ) )
							{
								if ( LPWSTR szMsg = DbGetEventTextW( &dbeiData, CP_ACP ) )
								{
									const size_t nMsgSize = ( wcslen( szMsg ) + 1 ) * sizeof( WCHAR );
									pEvent.SetCount( sizeof( EventData ) + nMsgSize );

									EventData& data = *(EventData*)pEvent.GetData();
									data.nFlags = dbeiData.flags;
									data.tTimestamp = dbeiData.timestamp;
									memcpy_s( data.wszText, nMsgSize, szMsg, nMsgSize );

									mir_free( szMsg );
								}
							}
							break;
						}
					}
				}
			}

			ATLTRACE( "PipeCommand() : Event #%d request from \"%s\"\n", nEventID, (LPCSTR)CT2A( (LPCTSTR)sName ) );

			if ( nCurrentEventID != nEventID )
				ATLTRACE( "PipeCommand() : Event not found! (last #%d)\n", nCurrentEventID );
		}
		else
		{
			// Get event list
			int nCount = 0, nTotalCount = 0;
			for ( HANDLE hEvent = GetFirstEvent( hFoundContact ); hEvent; hEvent = GetNextEvent( hEvent ) )
			{
				const int nSize = GetEventSize( hEvent );
				if ( nSize > 0 )
				{
					DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
					if ( ! GetEvent( hEvent, &dbei ) && dbei.eventType == EVENTTYPE_MESSAGE )
					{
						const size_t nOldSize = pEvent.GetCount();
						pEvent.SetCount( nOldSize + sizeof( time_t ) );

						time_t& tTimestamp = *(time_t*)( pEvent.GetData() + nOldSize );

						tTimestamp = dbei.timestamp;
						++nCount;
					}
				}
				++nTotalCount;
			}
			ATLTRACE( "PipeCommand() : Event list request from \"%s\" (selected %d events from %d)\n", (LPCSTR)CT2A( (LPCTSTR)sName ), nCount, nTotalCount );
		}
	}
	else
	{
		ATLTRACE( "PipeCommand() : Unknown contact \"%s\"!\n", (LPCSTR)CT2A( (LPCTSTR)sName ) );
		pEvent.Add( 0 ); // Error
	}

	return Send( hPipe, pEvent );
}

void PipeCommand(void* arg)
{
	CallService( MS_SYSTEM_THREAD_PUSH, 0, 0 );

	ATLTRACE( "PipeCommand() : Started\n" );

	CoInitialize( NULL );

	HANDLE hPipe = (HANDLE)arg;

	_AtlModule.RegisterClientPipe( hPipe );

	while ( ! Miranda_Terminated() )
	{
		Sleep( 0 );

		CAtlArray< BYTE > pRequest;
		if ( ! Recv( hPipe, pRequest ) )
			break;

		// Get list of contacts
		if ( IsCommand( pRequest, "/" ) )
		{
			if ( ! OnListRequest( hPipe ) )
				break;
		}
		// Get contact events
		else if ( pRequest.GetCount() > 2 && pRequest.GetAt( 0 ) == '/' )
		{
			if ( ! OnEventRequest( hPipe, pRequest.GetData(), pRequest.GetCount() ) )
				break;
		}
		// Who am I?
		else if ( IsCommand( pRequest, "WHOAMI?" ) )
		{
			if ( ! OnWhoAmIRequest( hPipe ) )
				break;
		}
		// Get version information
		else if ( IsCommand( pRequest, "VERSION?" ) )
		{
			if ( ! OnVersionRequest( hPipe ) )
				break;
		}
		// Unknown command
		else
		{
			ATLTRACE( "PipeCommand() : Unknown command!\n" ); 
		}
	}

	_AtlModule.CloseClientPipe( hPipe );

	CoUninitialize();

	ATLTRACE( "PipeCommand() : Terminated\n" );

	CallService( MS_SYSTEM_THREAD_POP, 0, 0 );
}

void PipeServer(void*)
{
	CallService( MS_SYSTEM_THREAD_PUSH, 0, 0 );

	ATLTRACE( "PipeServer() : Started\n" );

	CoInitialize( NULL );

	/*CDacl dacl;
	ATLVERIFY( dacl.AddDeniedAce( Sids::AnonymousLogon(), GENERIC_ALL ) );
	ATLVERIFY( dacl.AddDeniedAce( Sids::Network(), GENERIC_ALL ) );
	ATLVERIFY( dacl.AddAllowedAce( Sids::World(), GENERIC_ALL ) );
	CSecurityDesc desc;
	desc.SetDacl( dacl );
	desc.MakeAbsolute();
	CSecurityAttributes attrib( desc );*/

	bool bStarted = false;

	if ( HANDLE hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ) )
	{
		while ( ! Miranda_Terminated() )
		{
			Sleep( 0 );

			HANDLE hMainPipe = CreateNamedPipe( COMMAND_CHANNEL,
				PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
				PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT |
				( ( _AtlModule.m_OSVersion.dwMajorVersion >= 6 ) ? PIPE_REJECT_REMOTE_CLIENTS : 0 ),
				PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, NULL /*&attrib*/ );
			DWORD dwError = GetLastError();
			if ( hMainPipe == INVALID_HANDLE_VALUE )
			{
				// Re-create pipe on error after 1000 ms delay
				ATLTRACE( "PipeServer() : Failed to create pipe \"%s\" : %u\n", (LPCSTR)CT2A( COMMAND_CHANNEL ), dwError );
				Sleep( 1000 );
				continue;
			}

			ATLTRACE( "PipeServer() : Waiting for client on pipe \"%s\"\n", (LPCSTR)CT2A( COMMAND_CHANNEL ) );

			// Touch Windows Search
			if ( ! bStarted )
			{
				bStarted = true;
				mir_forkthread( &StartIndex, NULL );
			}

			OVERLAPPED over = {};
			over.hEvent = hEvent;
			BOOL bConnected = ConnectNamedPipe( hMainPipe, &over );
			dwError = GetLastError();
			if ( bConnected || dwError == ERROR_PIPE_CONNECTED )
			{
				// Accept client
				if ( mir_forkthread( &PipeCommand, hMainPipe ) )
				{
					// Do
					hMainPipe = INVALID_HANDLE_VALUE;
				}				
			}
			else if ( dwError == ERROR_IO_PENDING )
			{
				// Waiting for connection
				while ( ! Miranda_Terminated() )
				{
					// Wait for client connection for 100 ms and re-check termination status
					DWORD dwWait = WaitForSingleObject( hEvent, 100 );
					if ( dwWait != WAIT_TIMEOUT )
					{
						if ( dwWait == WAIT_OBJECT_0 )
						{
							// Accept client
							if ( mir_forkthread( &PipeCommand, hMainPipe ) )
							{
								// Do
								hMainPipe = INVALID_HANDLE_VALUE;
							}							
						}
						break;
					}
				}
			}

			if ( hMainPipe != INVALID_HANDLE_VALUE )
				CloseHandle( hMainPipe );
		}

		// Disconnect all waiting pipes
		_AtlModule.KickClientPipes();

		CloseHandle( hEvent );
	}

	CoUninitialize();

	ATLTRACE( "PipeServer() : Terminated\n" );

	CallService( MS_SYSTEM_THREAD_POP, 0, 0 );
}

int OnME_SYSTEM_MODULESLOADED(WPARAM /*wParam*/, LPARAM /*lParam*/)
{
	// Register in Updater plugin
	CallService( MS_UPDATE_REGISTERFL, FILE_ID, (LPARAM)&pluginInfo );

	// Add menu item
	CStringA serviceReIndex = _AtlModule.GetProductName() + "/ReIndex";
	CLISTMENUITEM item =
	{
		sizeof( CLISTMENUITEM ),
		(LPSTR)TranslateT( "Reindex" ),
		CMIF_TCHAR,
		0,
		(HICON)LoadImage( _AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCE( IDI_REINDEX ), IMAGE_ICON, 16, 16, LR_SHARED ),
		(LPSTR)(LPCSTR)serviceReIndex
	};
	CallService( MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM)&item );

	CallService( MS_CLIST_ADDMAINMENUITEM, 0, (LPARAM)&item );

	// Load existing names
	ReloadContacts();

	// Start command pipe server
	mir_forkthread( &PipeServer, 0 );

	return 0;
}

int OnME_DB_EVENT_ADDED(WPARAM wParam, LPARAM lParam)
{
	HANDLE hContact =  (HANDLE)wParam;
	HANDLE hEvent = (HANDLE)lParam;

	DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventCount", (DWORD)GetEventCount( hContact ) );
	DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventTime", GetEventTime( hEvent ) );

	const int index = GetEventNumber( hContact, hEvent );
	if ( index > 0 )
	{
		if ( LPCWSTR wszName = GetContactName( hContact ) )
		{
			CString sURL = GetCurrentUserURL() + URLEncode( wszName ) + _T("/");
			sURL.AppendFormat( _T("%d"), index );
			OnURLChanged( sURL, SEARCH_CHANGE_ADD, TRUE );
		}
	}

	return 0;
}

int OnME_DB_EVENT_DELETED(WPARAM wParam, LPARAM lParam)
{
	HANDLE hContact =  (HANDLE)wParam;
	HANDLE hEvent = (HANDLE)lParam;

	DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventCount", GetEventCount( hContact ) - 1 );
	DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventTime", GetEventTime( GetPrevEvent( hEvent ) ) );

	int index = GetEventNumber( hContact, hEvent );
	if ( index > 0 )
	{
		if ( LPCWSTR wszName = GetContactName( hContact ) )
		{
			const CString sBaseURL = GetCurrentUserURL() + URLEncode( wszName ) + _T("/");
			
			// Delete all events from specified to the end
			// (since numbers changed)
			for ( ; hEvent; hEvent = GetNextEvent( hEvent ) )
			{
				const int nSize = GetEventSize( hEvent );
				if ( nSize > 0 )
				{
					DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
					if ( ! GetEvent( hEvent, &dbei ) && dbei.eventType == EVENTTYPE_MESSAGE )
					{
						CString sURL;
						sURL.Format( _T( "%s%d" ), sBaseURL, index++ );
						OnURLChanged( sURL, SEARCH_CHANGE_DELETE );
					}
				}
			}
		}
	}

	return 0;
}

int OnME_DB_CONTACT_ADDED(WPARAM wParam, LPARAM /*lParam*/)
{
	HANDLE hContact =  (HANDLE)wParam;

	DBWriteContactSettingDword( NULL, _AtlModule.GetProductName(), "ContactCount", GetContactCount() );

	DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventCount", GetEventCount( hContact ) );
	DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventTime", GetEventTime( GetLastEvent( hContact ) ) );

	// No contact name yet

	return 0;
}

int OnME_DB_CONTACT_DELETED(WPARAM wParam, LPARAM /*lParam*/)
{
	HANDLE hContact =  (HANDLE)wParam;

	DBWriteContactSettingDword( NULL, _AtlModule.GetProductName(), "ContactCount", GetContactCount() - 1 );
	
	if ( LPCWSTR wszName = GetContactName( hContact ) )
	{
		const CString sBaseURL = GetCurrentUserURL() + URLEncode( wszName ) + _T( "/" );

		// Delete all events
		int index = 1;
		for ( HANDLE hEvent = GetFirstEvent( hContact ); hEvent; hEvent = GetNextEvent( hEvent ) )
		{
			const int nSize = GetEventSize( hEvent );
			if ( nSize > 0 )
			{
				DBEVENTINFO dbei = { sizeof( DBEVENTINFO ) };
				if ( ! GetEvent( hEvent, &dbei ) && dbei.eventType == EVENTTYPE_MESSAGE )
				{
					CString sURL;
					sURL.Format( _T( "%s%d" ), sBaseURL, index++ );
					OnURLChanged( sURL, SEARCH_CHANGE_DELETE );
				}
			}
		}

		// Delete contact itself
		OnURLChanged( sBaseURL, SEARCH_CHANGE_DELETE );
	}

	return 0;
}

int OnME_DB_CONTACT_SETTINGCHANGED(WPARAM wParam, LPARAM lParam)
{
	HANDLE hContact = (HANDLE)wParam;
	DBCONTACTWRITESETTING* dbcws = (DBCONTACTWRITESETTING*)lParam;

	if ( dbcws->szModule && _strcmpi( dbcws->szModule, "CList" ) == 0 &&
		 dbcws->szSetting && ( _strcmpi( dbcws->szSetting, "MyHandle" ) == 0 || _strcmpi( dbcws->szSetting, "Nick" ) == 0 ) )
	{
		// Remove old name
		for ( ;; )
		{
			const CString sMissingName = GetMissingContactName();
			if ( sMissingName.IsEmpty() )
				break;

			const CString sURL = GetCurrentUserURL() + URLEncode( sMissingName ) + _T( "/" );
			OnURLChanged( sURL, SEARCH_CHANGE_DELETE, TRUE );
		}
		ReloadContacts();

		// Add a new name
		if ( LPCWSTR wszName = GetContactName( hContact ) )
		{
			const CString sURL = GetCurrentUserURL() + URLEncode( wszName ) + _T("/");
			OnURLChanged( sURL, SEARCH_CHANGE_ADD, TRUE );
		}
	}

	return 0;
}

INT_PTR OnReIndex(WPARAM wParam, LPARAM /* lParam */)
{
	HANDLE hContact =  (HANDLE)wParam;
	
	DBWriteContactSettingDword( NULL, _AtlModule.GetProductName(), "ContactCount", GetContactCount() );

	if ( hContact )
	{
		DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventCount", GetEventCount( hContact ) );
		DBWriteContactSettingDword( hContact, _AtlModule.GetProductName(), "EventTime", GetEventTime( GetLastEvent( hContact ) ) );

		if ( LPCWSTR wszName = GetContactName( hContact ) )
		{
			CString sURL = GetCurrentUserURL() + URLEncode( wszName ) + _T("/");
			OnURLChanged( sURL, SEARCH_CHANGE_MODIFY, TRUE );
		}
	}
	else if ( MessageBox( GetDesktopWindow(), TranslateT("Do you want to reindex all messages again?"), CString( _AtlModule.GetProductName() ), MB_YESNO | MB_ICONQUESTION ) == IDYES )
	{
		OnURLChanged( GetCurrentUserURL(), SEARCH_CHANGE_MODIFY, TRUE );
	}

	return 0;
}

PLUGININFOEX* MirandaPluginInfoEx(DWORD /*mirandaVersion*/)
{
	return &pluginInfo;
}

const MUUID* MirandaPluginInterfaces(void)
{
	const static MUUID pluginInterfaces[] =
	{
		MIID_SEARCH,
		MIID_LAST
	};
	return pluginInterfaces;
}

int Load(PLUGINLINK* link)
{
	pluginLink = link;

	if ( mir_getMMI( &mmi ) )
		return -1;

	mir_getLP( &pluginInfo );

	SetHook( ME_SYSTEM_MODULESLOADED )
	SetHook( ME_DB_EVENT_ADDED )
	SetHook( ME_DB_EVENT_DELETED )
	SetHook( ME_DB_CONTACT_ADDED )
	SetHook( ME_DB_CONTACT_DELETED )
	SetHook( ME_DB_CONTACT_SETTINGCHANGED )

	SetService( ReIndex )

	return 0;
}

int Unload(void)
{
	RemoveHook( ME_SYSTEM_MODULESLOADED )
	RemoveHook( ME_DB_EVENT_ADDED )
	RemoveHook( ME_DB_EVENT_DELETED )
	RemoveHook( ME_DB_CONTACT_ADDED )
	RemoveHook( ME_DB_CONTACT_DELETED )
	RemoveHook( ME_DB_CONTACT_SETTINGCHANGED )

	RemoveService( ReIndex )

	return 0;
}
